Q1: On Mac OS X, how do I find out if a console user is
currently logged into the system?
Q2: How do I get notified of when the console user logs in
or logs out?
A1: You can obtain login/logout information and notification
using the System Configuration framework. Specifically,
you can obtain this information by watching for changes in
the console username. You can watch for console username changes using
the calls: SCDynamicStoreKeyCreateConsoleUser() and
CopyCurrentConsoleUsername . Note this information is
specific to the console user who is the user physically at the computer. Do
not confuse the console user with remote users who are logging in through
alternate means (for example, telent or ssh).
Here is how the console username key gives you login/logout information:
When no user is logged into Mac OS X the console username will be NULL.
When a user successfully logs into the system Mac OS X immediately sets the
console username to the username of the new user. Also, upon successful
logout Mac OS X immediately sets the console username back to NULL.
Note that successful logout occurs only after all processes have terminated,
and the logout can not be canceled.
Thus, by checking the console username at
any given moment in time you can determine if a user is logged
in or not. This determination is possible since the console username
will be non-NULL if a user is logged in and NULL if no user
is logged in. The below listing demonstrates how to determine
if a user is logged into the system or not.
The below function ConsoleUserIsLoggedIn()
will return true if there is a console user and false otherwise.
#include <CoreFoundation/CoreFoundation.h>#include <SystemConfiguration/SystemConfiguration.h>
/* CopyCurrentConsoleUsername
*
* This function will return the username of the
* console user who is currently logged in. Alternatively
* this function will return NULL if no user is currently
* logged into the system.
*/
CFStringRef CopyCurrentConsoleUsername()
{
CFStringRef consoleUserName;
uid_t uid;
gid_t gid;
/* Getting the username of current consoleuser. Note
* that if the username is NULL then that means that
* no user is presently logged in. We do the lookup
* using the SCDynamicStoreCopyConsoleUser call.
* First Argument: The dynamic store to use to lookup
* the console username. We only need a temporary dynamic
* store so pass null.
* Second Argument: On return this will have the
* UserID (UID) of the new user. We pass in a uid_t variable
* so we can get the return value. This value isn't used but
* is included for demonstration purposes.
* Third Argument: On return this will have the
* GroupID (GID) of the new user. We pass in a gid_t variable
* so we can get the return value. This value isn't used but
* is included for demonstration purposes.
* Return Value: The console username expressed as a CFString.
* This string must be eventually released with a CFRelease call.
*/
consoleUserName = SCDynamicStoreCopyConsoleUser(NULL, &uid, &gid);
return(consoleUserName);
}
/* ConsoleUserIsLoggedIn
*
* This function will return true if a console user is
* currently logged into the system, and false otherwise.
*/
Boolean ConsoleUserIsLoggedIn()
{
CFStringRef userName = CopyCurrentConsoleUsername();
if (userName == NULL)
{
//username is NULL thus no user is logged in
return(FALSE);
}
else
{
//username is non-NULL thus a console user is present
CFRelease(userName); //release username
return(TRUE);
}
}
|
Listing 1. Determining user login/logout status
|
A2:
Now what if you need to be notified of when a user logs in or logs out
of the system? This notification can be obtained by watching for
changes in the same console username. In this case you get
the System Configuration framework to notify you of any changes
to the console username. This is demonstrated in the code listing below.
The function you will be interested in code listing is the
InstallLoginLogoutNotifiers() function. This function takes
function pointers which represent the functions which get called when a login or
logout occurs. In the code I use generic functions LoginFunction and
LogoutFunction to receive the callbacks. However, you will likely want to
change or replace these generic functions with your own functions.
The InstallLoginLogoutNotifiers() call also returns a run loop
source which must be run as part of a run loop in order for the
callback functions to be called properly. Note the InstallLoginLogoutNotifiers()
is re-enterant.
#include <CoreFoundation/CoreFoundation.h>#include <SystemConfiguration/SystemConfiguration.h>
typedef void (*LoginNotificationCallBack)
(CFStringRef UserName, uid_t UID, gid_t GID);
typedef void (*LogoutNotificationCallBack) ();
struct LoginLogoutExtraInfoStructure
{
LoginNotificationCallBack loginFunctionToCall;
LogoutNotificationCallBack logoutFunctionToCall;
};
/*****************************************************
* LoginFunction
*****************************************************
* Purpose: This is the callback function which gets called any
* time a console user logs into the system. Note before this
* function will be called InstallLoginLogoutNotifiers must be called.
* Also, the CFRunLoopSource returned from InstallLoginLogoutNotifiers
* must be executed.
*
* Parameters:
*
* UserName A CFString Representing the new user. When
* LoginFunction is called this parameter will have a value
* representing the username of the user who just logged in.
*
* UID A integer representing the UID of the new user.
* When LoginFunction is called this parameter will have a value
* representing the UID of the user who just logged in.
*
* GID A integer representing the GID of the new user.
* When LoginFunction is called this parameter will have a value
* representing the GID of the user who just logged in.
*
*****************************************************/
void LoginFunction(CFStringRef UserName, uid_t UID, gid_t GID)
{
const int bufferSize = 80;
char usernameAsCString [bufferSize];
Boolean result = FALSE;
/* Now just for reference we will print out all the user information
* we have gathered. However first need to get a CString from
* the CFString since we are going to print to console.
* Thus, we need to get a CString from the CFString using the
* call CFStringGetCString.
* First Argument: The CFString to be converted to a CString. In this
* case we use are using the username passed to this function.
* Second Argument: An empty CString array which will be filled with
* the string which represents the CFString.
* Third Argument: The maximum size of the C String buffer.
* In this case we chose an arbitary length.
* Forth Argument: The encoding style to use when creating the
* CString. In this case since console understands ASCII we
* will stick with ASCII.
* Return Value: A boolean value representing if function
* was successful or not.
*/
result = CFStringGetCString(UserName, usernameAsCString,
bufferSize, kCFStringEncodingUTF8);
if (result == FALSE) //weren't able to create the CString
//successfully. so just have empty string for username.
{
usernameAsCString[0] = '\0';
}
//writing to console that login occurred as well as the
//accociated user information.
syslog(1,"Login Occurred.");
syslog(1,"User Information: Username:\t\t%s\nUID:\t\t%d\nGID:\t\t%d\n",
usernameAsCString, (int) UID, (int) GID);
}
/*****************************************************
* LoginFunction
*****************************************************
* Purpose: This is the callback function which gets called any
* time a console user logs out. Note before this
* function will be called InstallLoginLogoutNotifiers must be called.
* Also, the CFRunLoopSource returned from InstallLoginLogoutNotifiers
* must be executed.
*
* Parameters: No Parameters
*
*****************************************************/
void LogoutFunction()
{
//writing to console that logout occurred.
syslog(1,"Logout Occurred");
}
/*****************************************************
* LoginLogoutProxyCallBackFunction
*****************************************************
* Purpose: This callback function will be called on login or logout
* once InstallLoginLogoutNotifiers has been
* called and the returned CFRunloopsource is executed. This function
* will call the login and logout callback functions which were
* specified in the call InstallLoginLogoutNotifiers. In the case of
* the login notification function it will also pass the user name of
* the user who just logged in.
*
* Parameters: (All parameters are required if this function is to
* be called as a System Configuration framework
* callback function)
*
* store The dynamic store accociated with this
* callback. When LoginLogoutProxyCallBackFunction gets called the
* store variable will contain the dynamic store which is accociated
* with the callback.
*
* changedKeys A CFArray of keys that changed. Though given
* this parameter is unused in the function. This parameter could be
* used to determine what keys changed. However, we are only notifying
* on one key, namely the console username. Thus, we already know
* what the changed keys are.
* info A generic pointer pointing to data accociated
* with the callback. This is the dynamic store context we called
* InstallLoginLogoutNotifiers.
*****************************************************/
void LoginLogoutProxyCallBackFunction(SCDynamicStoreRef store,
CFArrayRef changedKeys, void * info)
{
#pragma unused (changedKeys)
CFStringRef consoleUserName;
uid_t consoleUserUID;
gid_t consoleUserGID;
LoginNotificationCallBack loginFunctionToCall;
LogoutNotificationCallBack logoutFunctionToCall;
struct LoginLogoutExtraInfoStructure* loginLogoutCallbackInfo;
/* Gathering the callback information from the dynamic store
* context information. This is stored in a
* LoginLogoutExtraInfoStructure which was allocated and
* setup in the InstallLoginLogoutNotifiers based on the
* callback functions passed to it.
*/
//Getting the callback structure from the void* passed.
loginLogoutCallbackInfo =
(struct LoginLogoutExtraInfoStructure*) info;
//Getting the login notification and logout notification callbacks from
//the dynamic store info passed to this function.
loginFunctionToCall = loginLogoutCallbackInfo->loginFunctionToCall;
logoutFunctionToCall = loginLogoutCallbackInfo->logoutFunctionToCall;
if ((loginFunctionToCall == NULL) || (logoutFunctionToCall == NULL))
{
//if the login or logout notification functions gathered are invalid
// then we need to fail here.
return;
}
/* Getting the username of who just logged in. Note that if
* the username is NULL then that means that the user just logged
* out. By checking the value of the console user using the
* dynamic store we can determine if someone just logged in
* (username is non-NULL), or logged out (username is NULL).
* We get the console username using the call
* SCDynamicStoreCopyConsoleUser
* First Argument: The dynamic store to use to lookup the
* console username. In this case the dynamic store which was
* passed to us.
* Second Argument: On return this will have the UserID (UID)
* of the new user. We pass in a uid_t variable so we can get
* the return value.
* Third Argument: On return this will have the Group ID (GID) of
* the new user. We pass in a gid_t variable so we can get the
* return value.
* Return Value: The console username expressed as a CFString.
* Note that this string must be released.
*/
consoleUserName = SCDynamicStoreCopyConsoleUser(store,
&consoleUserUID, &consoleUserGID);
//Now if the username is non-null then we know that someone
//just logged in. In this case call the login notification function.
if (consoleUserName != NULL)
{
loginFunctionToCall(consoleUserName, consoleUserUID, consoleUserGID);
CFRelease(consoleUserName); //done with console username so release.
}
else //Console username is null which means logout just occurred.
//Thus call the logout function instead.
{
logoutFunctionToCall();
}
}
/*****************************************************
* InstallLoginLogoutNotifiers
*****************************************************
* Purpose: This function will install the given Login/Logout
* notification functions. On successful install the login notification
* function will be called any time the console user logs. Also, the
* logout notification will be called any time the console user logs out.
* Notice that the notifications only apply to the console user who would
* be the user at the computer itself rather than a remote (telnet/ssh)
* user.
*
* Parameters:
* StringDescribingYourApplication A CFString string.
* Before calling InstallLoginLogoutNotifiers this parameter is expected
* to have either a value of NULL or contain a CFString which describes
* the calling application. Note that this is only an optional parameter
* used as the System Configuration framework debugging string in
* describing the calling process. This string in no way has to be unique
* or even present.
* Note that if a value of NULL is passed the InstallLoginLogoutNotifiers
* uses an empty string as the system configuration framework debugging
* string. Note it is strongly preferred that you actually pass a valid
* string since this may be used for getting more information in the future.
*
* YourLoginNotificationFunction A function pointer to a
* void function with a CFString argument. Before calling
* InstallLoginLogoutNotifiers the YourLoginNotificationFunction parameter
* is expected to have the name of a function which the user desires to be
* called any time a login occurs. Note that when called the CFString
* paramter in the login notification function will be set to the username
* of the user who just logged in. After calling this routine the login
* notification function will be called any time a console user logs in.
*
* YourLogoutNotificationFunction A function pointer to a
* void function no arguments. Before calling InstallLoginLogoutNotifiers
* the YourLogoutNotificationFunction parameter is expected to have the
* name of a function which the user desires to be called any time a logout
* occurs. After calling this routine the logout notification function
* will be called any time a console user logs out.
*
* RunloopSourceReturned When calling
* InstallLoginLogoutNotifiers this variable should be a pre-allocated
* CFRunLoopSourceRef. On return from InstallLoginLogoutNotifiers
* this variable will hold a RunLoopSource which must be added to
* a run-loop source and then run.
*
* A integer return value.
* See result codes listed below.
* Result Codes:
* -6 Error setting notification keys
* -5 Error creating Array of notification keys
* -4 Unable to lookup notification key
* for console username
* -3 Error allocating extra block to hold login/logout
* callback context information.
* -2 Unable to create dynamic store
* -1 Invalid login or logout notification function
* passed.
* 0 Sucess. Login/Logout notifiers were installed.
*
* Additional Note: Yes, this function is re-enterant. You can
* call it each time and get different runloop sources each time.
*
*****************************************************/
int InstallLoginLogoutNotifiers(
const CFStringRef StringDescribingYourApplication,
const LoginNotificationCallBack YourLoginNotificationFunction,
const LogoutNotificationCallBack YourLogoutNotificationFunction,
CFRunLoopSourceRef* RunloopSourceReturned)
{
CFStringRef SCDebuggingString = NULL;
SCDynamicStoreContext DynamicStoreContext =
{ 0, NULL, NULL, NULL, NULL };
SCDynamicStoreRef DynamicStoreCommunicationMechanism = NULL;
CFStringRef KeyRepresentingConsoleUserNameChange = NULL;
CFMutableArrayRef ArrayOfNotificationKeys;
Boolean Result;
struct LoginLogoutExtraInfoStructure* LoginLogoutCallBackFunctionInfo;
// --- Setting return variables to known values --- */
*RunloopSourceReturned = NULL;
// --- Checking input arguments to ensure they are valid --- //
//Checking the login and logout functions passed to make sure they
//are valid (i.e. not null).
if ((YourLoginNotificationFunction == NULL) ||
(YourLogoutNotificationFunction == NULL))
{
return(-1); //Invalid arguments passed. Returning error
}
/* The StringDescribingYourApplication is actually just a
* System Configuration framework debugging string.
* The string does not need to be unique or even present. However, if
* NULL is passed to this function
* we will insert empty string for the debugging string.
*/
if (StringDescribingYourApplication == NULL)
{
//Since passed null as the debugging string we assume an
//'empty' string for the debugging string.
SCDebuggingString = CFSTR("");
}
else
{
/* Since a actual string was passed we will use it as the debugging
* string. We create copy of the string passed in using
* CFStringCreateCopy.
* First Argument: Allocator to use. We want
* default as usual so pass null.
* Second Argument: The string to be duplicated. In this case we
* use the string passed in.
* Return Value: The new copy of the CFString in the
* second argument
*/
SCDebuggingString = CFStringCreateCopy(NULL,
StringDescribingYourApplication);
}
if (SCDebuggingString == NULL)
{
//Since error creating debugging string we assume an
//'empty' string for the debugging string.
SCDebuggingString = CFSTR("");
}
// --- Creating Dynamic Store Context --- //
/* Before we create the DynanicStoreCommunicationMechanism we will create
* the context information. The context information we will store
* and will be passed to the LoginLogoutCallbackFunction is the login
* and logout callbacks which it should call. We put this information
* in a SCDynamicStoreContext variable which will be added to the new
* dynamic store. First step is to allocate the strucutre to put in the
* dynamicStoreContext.
* Note that we can't deallocate this since it will be pointed to from
* the dynamic store itself and used in subsiquent callbacks.
*/
LoginLogoutCallBackFunctionInfo =
malloc(sizeof(struct LoginLogoutExtraInfoStructure));
if (LoginLogoutCallBackFunctionInfo == NULL)
{
//if were unable to allocate strucutre to hold login/logout callback
//information then give up here.
return(-3);
}
/* Now adding the callback information passed to this function to the
* "LoginLogoutExtraInfoStructure". This will then be added to the
* dynamic store context.
*/
LoginLogoutCallBackFunctionInfo->loginFunctionToCall =
YourLoginNotificationFunction;
LoginLogoutCallBackFunctionInfo->logoutFunctionToCall =
YourLogoutNotificationFunction;
/* Now adding the allocated structure to the dynamic store context. This
* context will be added to the dynamic store when it is created
*/
DynamicStoreContext.info = (void*) LoginLogoutCallBackFunctionInfo;
// --- Setting up notification with SystemConfiguration framework --- //
/* The next step is to set up the notification with the
* system configuration framework. The first step is to create
* a dynamic store. The dynamic store is used to communicate with the
* system configuration framework. Create dynamic store with a
* SCDynamicStoreCreate call.
*
* First Argument: Allocator to use. As usual we want default
* allocator so pass null.
* Second Argument: Before calling this must be a CFString to use as
* the debugging string in a debugging session. This will be the
* same string passed into this function to describe the
* application or just an empty string.
* Third Argument: This is the function which will be called whenever
* a key in the dynamic store changes. We have a proxy function
* which will get called whenever the console username changes.
* The name change will happen when a new user logs in or out.
* Thus the proxy function gets called any login or logout. The
* proxy function will in turn call the given Login/Logout
* functions passed to this function.
* Forth Argument: The dynamic store context information to be used
* when creating the dynamic store.
* Return Value: A mechanism used to comminicate with the dynamic
* store and the system configuration framework. This will be used
* in later System Configuration calls. Note this value must be
* released later with CFRelease.
*/
DynamicStoreCommunicationMechanism = SCDynamicStoreCreate
(NULL, SCDebuggingString, LoginLogoutProxyCallBackFunction,
&DynamicStoreContext);
//After creating the dynamic store we are done with the
//debugging string. Thus, releasing now.
CFRelease(SCDebuggingString);
if (DynamicStoreCommunicationMechanism == NULL)
{
//if DynamicStoreCommunicationMechanism is null then
//were unable to create communication mechanism. Fail here.
return(-2); //unable to create dynamic store.
}
/* Now we have a dynamic store communication mechanism we will need
* to get the keys with which we want to get notified upon. There is
* actually only one key in this case which is the console username.
* We get the console username key using the call
* SCDynamicStoreKeyCreateConsoleUser.
*
* Return Value: A key representing we want to be notified of
* console user name changes.
* Note that the return value has to be released.
*/
KeyRepresentingConsoleUserNameChange =
SCDynamicStoreKeyCreateConsoleUser(NULL);
if (KeyRepresentingConsoleUserNameChange == NULL)
//Note: if key is null we fail.
{
CFRelease(DynamicStoreCommunicationMechanism);
//releasing allocated memory before giving up.
return(-4);
}
// --- creating array of notification keys --- //
/* Now that we have the console user key which is the key we will
* be notifing upon we need to place the
* key into an array. This is because the System Configuration
* framework expects all lists of keys to be in an array. Thus we
* will create an array for the occasion then place the key inside
* of it. We create the array with CFArrayCreateMutable.
* First Argument: Allocator to use. As usual we want default
* allocator so pass null.
* Second Argument: The number of items which will be in the array.
* In this case we only have one key so the size will be one.
* Third Argument: The type of retain/release methods to use with the
* data. In this case we are using normal Core Foundation types
* which means default CFArray behavior will do. Thus, as
* retain/release behavior we pick kCFTypeArrayCallBacks which
* represents the normal retain/release for an CFArray.
* Return Value: An empty CFArray. This is the array which will
* hold the notification keys. Note the CFArray must be released.
*/
ArrayOfNotificationKeys = CFArrayCreateMutable
(NULL, (CFIndex)1, &kCFTypeArrayCallBacks);
/* error creating CFArray of notification keys. */
if (ArrayOfNotificationKeys == NULL)
{
CFRelease(DynamicStoreCommunicationMechanism);
//releasing allocated memory before giving up.
CFRelease(KeyRepresentingConsoleUserNameChange);
return(-5);
}
/* Now that we have our empty array we need to add the notification
* key to it. We do this using CFArrayAppendValue
* First Argument: The CFArray which will have an item appended to it.
* Second Argument: Key which will be added to CFArray. The key is
* represented as CFString.
* No return value
*/
CFArrayAppendValue(ArrayOfNotificationKeys,
KeyRepresentingConsoleUserNameChange);
/* Now that we have an array repesenting the values to notify upon
* we will add this information to the dynamic store. This way we
* can create a CFRunloop source based upon the notification key
* chosen. In this case the notification key was the console
* user change. We add the notification keys to the
* dynamic store with the call SCDynamicStoreSetNotificationKeys
* First Argument: Dynamic store to add this information to.
* This is the dynamic store we obtained earlier.
* Second Argument: The Array of notification keys. In this case
* the single key representing any change in console user.
* Third Argument: Any regex keys to be monitored. In this case we
* already have all our keys we need from the array. Thus this
* value can be NULL since we don't need any other keys.
* Return Value: A boolean representing if operation was successful
* (true) or unsuccessful (false).
*/
Result = SCDynamicStoreSetNotificationKeys(
DynamicStoreCommunicationMechanism, ArrayOfNotificationKeys, NULL);
//Done with notification keys array so release
CFRelease(ArrayOfNotificationKeys);
//done with notification key so release.
CFRelease(KeyRepresentingConsoleUserNameChange);
if (Result == FALSE) //unable to add keys to dynamic store.
{
//releasing allocated memory before giving up.
CFRelease(DynamicStoreCommunicationMechanism);
return(-6);
}
/* Now we are creating the CFRunloopSource which we will return on
* this function. This run loop source when inserted in your run loop
* will cause your notification functions to get called via the
* 'proxy' notification function. We create the RunLoopSource
* using the call SCDynamicStoreCreateRunLoopSource.
*
* First Argument: Allocator to use. As usual we want default
* allocator so pass null.
* Second Argument: The dynamic store which to base the run loop
* source upon. We have already created a dynamic store which
* will notify on console user changes. This will be the dynamic
* store the run loop source is based upon.
* Third Argument: The 'order' of the CFRunloop source. We want
* default behavior here too so pass zero.
* Return value: the run loop source created. Note that the
* return value must eventually be released.
*/
*RunloopSourceReturned = SCDynamicStoreCreateRunLoopSource
(NULL, DynamicStoreCommunicationMechanism, (CFIndex) 0);
return(0); //if got this far were succesful.
}
int main (int argc, const char * argv[])
{
int result;
CFRunLoopSourceRef testRunloopSource = NULL;
/* Want this application to run across logout. Therefore make
* it a daemon by calling daemon(). Note all daemon really does is
* makes the Parent process init so that loginwindow won't
* terminate this function at logout.
*/
daemon(0,0);
/* Installing the login logout notifiers into the CFRunloop source.
* The notifier functions will be called whenever login or logout
* occurs.
* First Argument: A CFString describing the program which is running.
* Second Argument: The login notification function which will be
* called on login. Replace this with your function if you like.
* Third Argument: The logout notification function which will
* be called on successful logout. Replace this with your
* function if you like.
* Forth Argument: On return this will have a runloopsource which
* you can use to create a runloop which will call your notificaiton
* functions in 2nd and 3rd arguments.
* Return Value: Integer representing if function was successful
* or not. Value is zero on successful.
*/
result = InstallLoginLogoutNotifiers(
CFSTR("This is my crazy program"), LoginFunction,
LogoutFunction, &testRunloopSource);
if (result != 0)
{
return(1); //give up since could not install notifier.
}
/* Adding a run loop source to the current run loop.
* Here we are adding our login/logout notifications to the current
* run loop.
* First Argument: The run loop to add the new source to. In this
* case we want the default run loop for this process which is
* current run loop.
* Second Argument: The run loop source which will be added to
* current run loop. This is the run loop which we created earlier
* it contains login/logout notification function information.
* Third Argument: The run loop mode. As usual we want default mode.
* Thus pass kCFRunLoopDefaultMode to get default mode.
*/
CFRunLoopAddSource(CFRunLoopGetCurrent(),
testRunloopSource, kCFRunLoopDefaultMode);
//Done with run loop source so release.
CFRelease(testRunloopSource);
//Running the run loop. This is required so that the
//login/logout functions get called.
CFRunLoopRun();
return 0;
}
|
Listing 2. Registering for login/logout notifications
|
Note in the above code the login and logout notifier
functions write to the console using the syslog command. By default what
these functions write at login and logout won't show up in Console.app
when it is launched. This is because Console.app only shows console
information written after login has completed. To see the full
console log type: "open /var/log/system.log " into terminal.
Scroll down to the bottom of the log to look for the console output
from the program.
Note that all the above discussion is only valid in
Mac OS X 10.1 and later. This is because the System Configuration
framework only became available in Mac OS X 10.1.
Downloadables
IsUserLoggedIn.sit
|
Source project for code listing one (8K)
|
Download
|
LoginLogoutNotification.sit
|
Source project for code listing two (12K)
|
Download
|
[Apr 08 2002]
|